require 'securerandom'

module Permalinkable
  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods
    def acts_as_permalinkable(options = {})
      send :cattr_accessor, :permalink_options
      self.permalink_options = { permalinkable_attribute: :name, permalink_field_name: :permalink,
                                 length: 200, allow_change: false }
      self.permalink_options.update(options) if options.is_a?(Hash)
      self.permalink_options.freeze

      send :include, InstanceMethods
      send :after_save, :save_permalink
      send :attr_readonly, permalink_options[:permalink_field_name]
      send :validates, permalink_options[:permalinkable_attribute], presence: true, length: { minimum: 0 }
    end

    def permalink_cipher
      cipher = OpenSSL::Cipher.new('RC4-40')
      cipher.encrypt
      cipher.key = Base64.urlsafe_decode64(Configure.secret)
      cipher
    end
  end

  module InstanceMethods
    def to_param
      existing_permalink = send(permalink_options[:permalink_field_name])
      (existing_permalink.present? && existing_permalink) || id.to_s
    end

    private
    def generate_permalink
      sanitized = self.send(permalink_options[:permalinkable_attribute]).gsub(/[^[:alnum:]]/, ' ').strip.gsub(/\W+/, '-')
      "#{fpe}-#{sanitized}"[0..permalink_options[:length]]
    end

    def save_permalink
      generated_permalink = generate_permalink
      if self.send(permalink_options[:permalink_field_name]).blank? || \
           ( permalink_options[:allow_change] && generated_permalink != self.send(permalink_options[:permalink_field_name]) )
        self.class.unscoped.where(self.class.primary_key => self.id).update_all( permalink_options[:permalink_field_name].to_s => generated_permalink )
        raw_write_attribute(permalink_options[:permalink_field_name].to_s, generated_permalink)
      end
    end

    def fpe
      @fpe ||= Base64.urlsafe_encode64(self.class.permalink_cipher.update('%11s' % self.id))
    end
  end
end
